package org.g4studio.core.orm.xibatis.sqlmap.engine.config;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.g4studio.core.orm.xibatis.sqlmap.client.extensions.TypeHandlerCallback;
import org.g4studio.core.orm.xibatis.sqlmap.engine.impl.SqlMapClientImpl;
import org.g4studio.core.orm.xibatis.sqlmap.engine.impl.SqlMapExecutorDelegate;
import org.g4studio.core.orm.xibatis.sqlmap.engine.mapping.result.Discriminator;
import org.g4studio.core.orm.xibatis.sqlmap.engine.mapping.result.ResultMap;
import org.g4studio.core.orm.xibatis.sqlmap.engine.mapping.result.ResultMapping;
import org.g4studio.core.orm.xibatis.sqlmap.engine.scope.ErrorContext;
import org.g4studio.core.orm.xibatis.sqlmap.engine.type.CustomTypeHandler;
import org.g4studio.core.orm.xibatis.sqlmap.engine.type.TypeHandler;
import org.g4studio.core.orm.xibatis.sqlmap.engine.type.TypeHandlerFactory;

public class ResultMapConfig {
	private SqlMapConfiguration config;
	private ErrorContext errorContext;
	private SqlMapClientImpl client;
	private SqlMapExecutorDelegate delegate;
	private TypeHandlerFactory typeHandlerFactory;
	private ResultMap resultMap;
	private List resultMappingList;
	private int resultMappingIndex;
	private Discriminator discriminator;

	ResultMapConfig(SqlMapConfiguration config, String id, Class resultClass, String groupBy, String extendsResultMap,
			String xmlName) {
		this.config = config;
		this.errorContext = config.getErrorContext();
		this.client = config.getClient();
		this.delegate = config.getDelegate();
		this.typeHandlerFactory = config.getTypeHandlerFactory();
		this.resultMap = new ResultMap(client.getDelegate());
		this.resultMappingList = new ArrayList();
		errorContext.setActivity("building a result map");
		errorContext.setObjectId(id + " result map");
		resultMap.setId(id);
		resultMap.setXmlName(xmlName);
		resultMap.setResource(errorContext.getResource());
		if (groupBy != null && groupBy.length() > 0) {
			StringTokenizer parser = new StringTokenizer(groupBy, ", ", false);
			while (parser.hasMoreTokens()) {
				resultMap.addGroupByProperty(parser.nextToken());
			}
		}
		resultMap.setResultClass(resultClass);
		errorContext.setMoreInfo("Check the extended result map.");
		if (extendsResultMap != null) {
			ResultMap extendedResultMap = (ResultMap) client.getDelegate().getResultMap(extendsResultMap);
			ResultMapping[] resultMappings = extendedResultMap.getResultMappings();
			for (int i = 0; i < resultMappings.length; i++) {
				resultMappingList.add(resultMappings[i]);
			}
			List nestedResultMappings = extendedResultMap.getNestedResultMappings();
			if (nestedResultMappings != null) {
				Iterator iter = nestedResultMappings.iterator();
				while (iter.hasNext()) {
					resultMap.addNestedResultMappings((ResultMapping) iter.next());
				}
			}
			if (groupBy == null || groupBy.length() == 0) {
				if (extendedResultMap.hasGroupBy()) {
					Iterator i = extendedResultMap.groupByProps();
					while (i.hasNext()) {
						resultMap.addGroupByProperty((String) i.next());
					}
				}
			}
		}
		errorContext.setMoreInfo("Check the result mappings.");
		resultMappingIndex = resultMappingList.size();
		resultMap.setResultMappingList(resultMappingList);
		client.getDelegate().addResultMap(resultMap);
	}

	public void setDiscriminator(String columnName, Integer columnIndex, Class javaClass, String jdbcType,
			String nullValue, Object typeHandlerImpl) {
		TypeHandler handler;
		if (typeHandlerImpl != null) {
			if (typeHandlerImpl instanceof TypeHandlerCallback) {
				handler = new CustomTypeHandler((TypeHandlerCallback) typeHandlerImpl);
			} else if (typeHandlerImpl instanceof TypeHandler) {
				handler = (TypeHandler) typeHandlerImpl;
			} else {
				throw new RuntimeException(
						"The class '' is not a valid implementation of TypeHandler or TypeHandlerCallback");
			}
		} else {
			handler = config.resolveTypeHandler(client.getDelegate().getTypeHandlerFactory(),
					resultMap.getResultClass(), "", javaClass, jdbcType, true);
		}
		ResultMapping mapping = new ResultMapping();
		mapping.setColumnName(columnName);
		mapping.setJdbcTypeName(jdbcType);
		mapping.setTypeHandler(handler);
		mapping.setNullValue(nullValue);
		mapping.setJavaType(javaClass);
		if (columnIndex != null) {
			mapping.setColumnIndex(columnIndex.intValue());
		}
		discriminator = new Discriminator(delegate, mapping);
		resultMap.setDiscriminator(discriminator);
	}

	public void addDiscriminatorSubMap(Object value, String resultMap) {
		if (discriminator == null) {
			throw new RuntimeException("The discriminator is null, but somehow a subMap was reached.  This is a bug.");
		}
		discriminator.addSubMap(value.toString(), resultMap);
	}

	public void addResultMapping(String propertyName, String columnName, Integer columnIndex, Class javaClass,
			String jdbcType, String nullValue, String notNullColumn, String statementName, String resultMapName,
			Object impl) {
		errorContext.setObjectId(propertyName + " mapping of the " + resultMap.getId() + " result map");
		TypeHandler handler;
		if (impl != null) {
			if (impl instanceof TypeHandlerCallback) {
				handler = new CustomTypeHandler((TypeHandlerCallback) impl);
			} else if (impl instanceof TypeHandler) {
				handler = (TypeHandler) impl;
			} else {
				throw new RuntimeException("The class '" + impl
						+ "' is not a valid implementation of TypeHandler or TypeHandlerCallback");
			}
		} else {
			handler = config.resolveTypeHandler(client.getDelegate().getTypeHandlerFactory(),
					resultMap.getResultClass(), propertyName, javaClass, jdbcType, true);
		}
		ResultMapping mapping = new ResultMapping();
		mapping.setPropertyName(propertyName);
		mapping.setColumnName(columnName);
		mapping.setJdbcTypeName(jdbcType);
		mapping.setTypeHandler(handler);
		mapping.setNullValue(nullValue);
		mapping.setNotNullColumn(notNullColumn);
		mapping.setStatementName(statementName);
		mapping.setNestedResultMapName(resultMapName);
		if (resultMapName != null && resultMapName.length() > 0) {
			resultMap.addNestedResultMappings(mapping);
		}
		mapping.setJavaType(javaClass);
		if (columnIndex != null) {
			mapping.setColumnIndex(columnIndex.intValue());
		} else {
			resultMappingIndex++;
			mapping.setColumnIndex(resultMappingIndex);
		}
		resultMappingList.add(mapping);
		resultMap.setResultMappingList(resultMappingList);
	}

}
